<?php
/********************************************************************
 *	Author:
 *		Jestery Liu <jestery@gmail.com>
 *		http://www.pte.cn
 *
 *	File:			BaseObject.class.php
 *	Create Date:	2006-12-31 12:22:04 CST
 *******************************************************************/


/**
 * 该类对应数据库里的一条记录。可以继承该类，也可以直接使用。
 * 
 * @author Jestery <jestery@gmail.com>
 * 
 * 直接使用方法：
 * <code>
 * <?php
 * $bo = new BaseObject(array('table'=>'my_table', 'primaryKey', 'book_id'));
 * $bo->setDb($db);
 * $book = &$bo->getEmptyObject();
 * $book->name = 'How to get rich';
 * $book->author = 'james';
 * $book->isbn = '1234-5678';
 * $book->price = '25.99';
 * $book->save();
 * ?>
 * </code>
 * 
 * 继承使用方法：
 * <code>
 * <?php
 * class Book extends BaseObject
 * {
 * 		public $table = 'book';
 * 		public function __construct()
 * 		{
 * 			parent::__construct();
 * 		}
 * 		protected function onUpdate()
 * 		{
 * 			echo $this->isbn.'has been updated.';
 * 		}
 * }
 * $book = new Book();
 * $book->setDb($db);
 * $book = $book->getObjectByPk(7);
 * $book->isbn = '4567-123';
 * $book->save();
 * ?>
 * </code>
 *
 */
class BaseObject
{
	/**
	 * 数据表主键名称。
	 *
	 * @var string
	 */
	public $primaryKey = 'id';
	/**
	 * 数据表名称
	 *
	 * @var string
	 */
	public $table = '';

	protected $_className = '';
	protected $_isNewObject = false;
	protected $_fields = array();
	protected $_fieldsOrig = array();
	protected $db = null;

	/**
	 * 构造函数。
	 *
	 * @param array $options 一个键/值对的数组。键名可以为 primaryKey或者table。例如 array('primaryKey'=>'book_id', 'table'=>'books');
	 */
	public function __construct(array $options=array())
	{
		$this->_className = get_class($this);
		if (count($options)>0)
		{
			foreach ($options as $k=>$v)
			{
				if (isset($this->$k))
					$this->$k = $v;
			}
		}
	}
	
	/**
	 * 设置数据库类实例
	 *
	 * @param DB $db
	 */
	public function setDb($db) {
		$this->db = $db;
	}

	/**
	 * 设置数据表主键名称
	 *
	 * @param string $keyName 主键字段名称
	 */
	public function setPrimaryKey($keyName)
	{
		$this->primaryKey = $keyName;
	}

	/**
	 * 获取一个空的数据库记录实例，用来添加记录。想要添加一条记录之前，必须先获得空的对象实例
	 *
	 * @return BaseObject
	 */
	public function &getEmptyObject()
	{
		$id = $this->db->nextId("{$this->table}");
		$object = new $this->_className;
		$object->{$this->primaryKey} = $id;
		$object->_isNewObject = true;
		return $object;
	}

	/**
	 * 通过主键来获取一个记录实例。
	 *
	 * @param mixed $id 要获取的记录的主键的值
	 * @param string $fields 要获取的字段。同SQL语法的 select field1, field2...
	 * @return BaseObject|false
	 */
	public function &getObjectByPk($id, $fields='*')
	{
		try {
			$res = &$this->db->getRow("select {$fields} from {$this->table} where {$this->primaryKey}=?", array($id));
		} catch (DBException $e) {
			return false;
		}
		if (!is_array($res))
			return false;
		return $this->_mapRow($res);
	}

	/**
	 * 对更改过的数据库记录实例进行保存
	 *
	 * @return bool
	 */
	public function save()
	{
		if ($this->_isNewObject)
			return $this->_addNew();
		else
			return $this->_update();
	}

	/**
	 * 根据主键来判断一条记录是否存在
	 *
	 * @param mixed $id 要判断的记录的主键值
	 * @return bool
	 */
	public function exists($id)
	{
		$res = $this->db->getOne("select count(*) from {$this->table} where {$this->primaryKey}=?",array($this->{$this->primaryKey}), array($id));
		return $res>0;
	}

	/**
	 * 判断一个字段自从数据库读取出来之后是否被修改过
	 *
	 * @param string $fieldName 字段名称
	 * @return bool
	 */
	public function isModified($fieldName)
	{
		return $this->_fields[$fieldName] !== $this->_fieldsOrig[$fieldName] ? true : false;
	}

	/**
	 * 判断当前实例是新记录还是从数据库读出的旧记录
	 *
	 * @return bool
	 */
	public function isNew()
	{
		return $this->_isNewObject;
	}

	/**
	 * 对记录执行更新操作之后自动执行此方法。可以在本类的继承类中覆盖此方法。如果覆盖此方法，方法成功或者失败后一定要返回 true 或者 false
	 *
	 * @return bool
	 */
	protected function onUpdate()
	{
		return true;
	}

	/**
	 * 对一个新记录执行添加操作之后自动执行此方法。可以在本类的继承类中覆盖此方法。如果覆盖此方法，方法成功或者失败后一定要返回 true 或者 false。
	 *
	 * @return bool
	 */
	protected function onAddNew()
	{
		return true;
	}

	/**
	 * 对一个记录进行删除操作之后自动执行此方法。可以在本类的继承类中覆盖此方法。如果覆盖此方法，方法成功或者失败后一定要返回 true 或者 false。
	 *
	 * @return bool
	 */
	protected function onRemove()
	{
			return true;
	}

	/**
	 * 将数据库记录映射成实例成员
	 *
	 * @param array $res
	 * @return BaseObject
	 * @access protected
	 */
	protected function &_mapRow(&$res)
	{
		$obj = new $this->_className;
		foreach ($res as $k=>$v)
			$obj->_fields[$k] = $v;

		$obj->_fieldsOrig = $obj->_fields;
		unset($res);
		return $obj;
	}

	protected function _update()
	{
		$update = array();
		foreach ($this->_fields as $field=>$v)
		{
			if ($this->isModified($field))
			{
				$update[$field] = $v;
			}
		}
		if (count($update)<1)
		{
			return true;
		}
		else
		{
			try {
				$res = $this->db->autoExecute($this->table, $update, DB::AUTOQUERY_UPDATE, "{$this->primaryKey}='".$this->{$this->primaryKey}."'");
			} catch(DBException $e) {
				unset($update);
				return false;
			}
			unset($update);
			return $this->onUpdate() && true;
		}
	}

	protected function _addNew()
	{
		try {
			$res = $this->db->autoExecute($this->table, $this->_fields, DB::AUTOQUERY_INSERT);
		} catch (DBException $e) {
			return false;
		}
		return $this->onAddNew() && true;
	}

	/**
	 * 删除本类实例所代表的记录
	 *
	 * @return bool
	 */
	public function remove() {
		try {
			$res = $this->db->query("delete from {$this->table} where {$this->primaryKey}=?", array($this->{$this->primaryKey}));
		} catch (Exception $e) {
			return false;
		}
		return $this->onRemove && true;
	}

	protected function __get($var)
	{
		if (!isset($this->$var))
		{
			if (StringHelper::strEndsWith($var, '_orig'))
				return $this->_fieldsOrig[$var];
			else
				return $this->_fields[$var];
		}
		else
			return $this->$var;
	}

	protected function __set($vari, $val)
	{
		if (!isset($this->$vari))
			$this->_fields[$vari] = $val;
		else
			$this->$vari = $val;
	}
}

/* vim: set ts=4 sw=4 ff=unix: */
